﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Cjc.ExpressionEngine.Excel.Runtime.Operations;
using Cjc.ExpressionEngine.Excel.Runtime.Types;
using Microsoft.Linq.Expressions;
using Microsoft.Scripting.Actions;
using Microsoft.Scripting.Actions.Calls;
using Microsoft.Scripting.Generation;
using Microsoft.Scripting.Runtime;
using System.Reflection;
using Microsoft.Scripting;

namespace Cjc.ExpressionEngine.Excel.Runtime.Binding
{
	public class ExcelBinder : DefaultBinder
	{
		private static Dictionary<Type, Type[]> extensionTypes;
		private static Type[] types = new[] { typeof( Range ), typeof( CellId ), typeof( Cell ) };

		static ExcelBinder()
		{
			extensionTypes = new Dictionary<Type, Type[]>();
			extensionTypes[ typeof( decimal ) ] = new[] { typeof( DecimalOps ) };
			extensionTypes[ typeof( double ) ] = new[] { typeof( DoubleOps ) };
			extensionTypes[ typeof( long ) ] = new[] { typeof( BigIntOps ) };
			extensionTypes[ typeof( int ) ] = new[] { typeof( Int32Ops ), typeof( DecimalOps ) };
			extensionTypes[ typeof( string ) ] = new[] { typeof( StringOps ) };
			extensionTypes[ None.Type ] = new[] { typeof( NoneTypeOps ) };
		}

		public ExcelBinder( ScriptDomainManager manager ) : base( manager )
		{
		}

		public override IList<Type> GetExtensionTypes( Type t )
		{
			IEnumerable<Type> types = new[] { t }.Union( base.GetExtensionTypes( t ) );

			return extensionTypes.ContainsKey( t )
				? types.Union( extensionTypes[ t ] ).ToList()
				: types.ToList();
		}

		public override bool CanConvertFrom( Type fromType, Type toType, bool toNotNullable, NarrowingLevel level )
		{
			if ( fromType == None.Type ) return true;
			if ( fromType == toType || toType.IsAssignableFrom( fromType ) ) return true;
			if ( fromType == typeof( Range ) && typeof( IEnumerable ).IsAssignableFrom( toType ) ) return true;
			if ( fromType == typeof( CellId ) || fromType == typeof( Cell ) ) return true;

			var fromCode = Type.GetTypeCode( fromType );
			var toCode = Type.GetTypeCode( toType );

			if ( fromCode == TypeCode.Int32
				&& ( toCode == TypeCode.Int64 || toCode == TypeCode.Double || toCode == TypeCode.Decimal ) )
				return true;

			if ( fromCode == TypeCode.Double
				&& toCode == TypeCode.Decimal )
				return true;

			if ( fromCode == TypeCode.String || toCode == TypeCode.String )
				return true;

			return CompilerHelpers.HasImplicitConversion( fromType, toType );
		}

		public override Candidate PreferConvert( Type t1, Type t2 )
		{
			if ( typeof( IEnumerable ).IsAssignableFrom( t1 ) && typeof( IEnumerable ).IsAssignableFrom( t2 ) )
			{
				var t1Arg = t1.GetGenericArguments().FirstOrDefault();
				var t2Arg = t2.GetGenericArguments().FirstOrDefault();

				if ( t1Arg == typeof( Cell ) ) return Candidate.One;
				if ( t2Arg == typeof( Cell ) ) return Candidate.Two;
			}

			return Candidate.Equivalent;
		}

		public override Candidate SelectBestConversionFor( Type actualType, ParameterWrapper candidateOne, ParameterWrapper candidateTwo, NarrowingLevel level )
		{
			return Candidate.Equivalent;
/*
			if ( actualType == candidateOne.Type ) return Candidate.One;
			if ( actualType == candidateTwo.Type ) return Candidate.Two;

			if ( candidateOne.Type == typeof( string ) ) return Candidate.Two;
			if ( candidateTwo.Type == typeof( string ) ) return Candidate.One;

			return base.SelectBestConversionFor( actualType, candidateOne, candidateTwo, level );*/
		}

		public override object Convert( object obj, Type toType )
		{
			return base.Convert( obj, toType );
		}

		public override Expression ConvertExpression( Expression expr, Type toType, ConversionResultKind kind, Expression context )
		{
/*			if ( expr.Type != toType && types.Contains( expr.Type ) )
			{
				var worksheet = Expression.Property(
					Expression.ConvertHelper( Expression.Property( context, "Scope" ), typeof( ExcelScope ) ),
					"Worksheet" );

				Expression cell = null;

				if ( expr.Type == typeof( Range ) )
				{
					var cells = Expression.Call(
						expr,
						typeof( Range ).GetMethod( "GetCells", BindingFlags.Instance | BindingFlags.Public ),
						worksheet );

					if ( toType == typeof( IEnumerable<Cell> ) ) return cells;

					cell = Expression.Call(
						typeof( System.Linq.Enumerable ).GetMember( "First" ).Cast<MethodInfo>().First().MakeGenericMethod( typeof( Cell ) ),
						cells );
				}
				else if ( expr.Type == typeof( CellId ) )
				{
					cell = Expression.Call( expr, typeof( CellId ).GetMethod( "GetCell" ), worksheet );
				}

				if ( toType == typeof( Cell ) ) return cell;

				return ConvertExpressionInternal(
					Expression.Property( cell, "Value" ),
					toType,
					kind,
					context );
			}

			return ConvertExpressionInternal( expr, toType, kind, context );
		}
/*
		private Expression GetCellReadExpression( string key )
		{
			return Expression.Call(
				Expression.Constant( new CellBinder( key ) ),
				typeof( CellBinder ).GetMethod( "Invoke" ),
				Utils.CodeContext() );
		}*/
/*
		private Expression ConvertExpressionInternal( Expression expr, Type toType, ConversionResultKind kind, Expression context )
		{*/
			var exprType = expr.Type;

			if ( toType == typeof( object ) )
			{
				return ( exprType.IsValueType )
					? Expression.ConvertHelper( expr, toType )
					: expr;
			}

			if ( toType.IsAssignableFrom( exprType ) )
			{
				return expr;
			}

			var visType = CompilerHelpers.GetVisibleType( toType );

			return Expression.Dynamic(
				new ConversionBinder( this, context, visType, kind ),
				visType,
				expr );
		}
	}
}